package net.villonanny;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.configuration.AbstractFileConfiguration;
import org.apache.commons.configuration.CombinedConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.FileConfiguration;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.configuration.tree.NodeCombiner;
import org.apache.commons.configuration.tree.UnionCombiner;
import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.slf4j.helpers.MessageFormatter;

public class ConfigManager {
	private final static Logger log = Logger.getLogger(ConfigManager.class);
	private final static String CONFIGFILE = "configuration.xml";
	public final static String CONFIGDIR = "config";
	private final static int RELOAD_TIMEOUT_SECONDS = 4; // Check no faster than every 4 seconds

	private static HierarchicalConfiguration mainConfig = null;
	public static XMLConfiguration xmlPart = null;

//	static {
//		mainConfig = loadConfiguration();
//	}
	
	public static boolean isConfigurationThere() {
		File configFile = new File(CONFIGDIR + File.separatorChar + ConfigManager.CONFIGFILE);
		return configFile.exists();
	}
	
	public static boolean createNewConfiguration(String[] args) {
		File configFile = new File(CONFIGDIR + File.separatorChar + ConfigManager.CONFIGFILE);
		try {
			mainConfig = init(null);
		} catch (ConfigurationException e) {
			log.error("Configuration error", e);
		}
		return new AutoConfigurator().configure(configFile);
	}
	
	public static void createEmptyConfiguration() {
		try {
			mainConfig = init(null);
		} catch (ConfigurationException e) {
			log.error("Configuration error", e);
		}
	}
	
	private static CombinedConfiguration init(String configFile) throws ConfigurationException {
		NodeCombiner combiner = new UnionCombiner();
		CombinedConfiguration tmpConfig = new CombinedConfiguration(combiner);
		tmpConfig.setExpressionEngine(new XPathExpressionEngine());
		tmpConfig.setForceReloadCheck(true);
		if (configFile!=null) {
			xmlPart = new XMLConfiguration(configFile);
		} else {
			xmlPart = new XMLConfiguration();
		}
		xmlPart.setExpressionEngine(new XPathExpressionEngine());
		tmpConfig.addConfiguration(xmlPart);
		return tmpConfig;
	}
	
	public static void loadConfiguration() {
		String s = "Loading configuration..."; 
		log.info(s);
		EventLog.log(s);
		try {
			CombinedConfiguration tmpConfig = init(CONFIGFILE);
			
			log.debug("Setting reload interval to " + RELOAD_TIMEOUT_SECONDS);
			FileChangedReloadingStrategy reloadingStrategy = new FileChangedReloadingStrategy();
			reloadingStrategy.setRefreshDelay(RELOAD_TIMEOUT_SECONDS * 1000);
			
			((FileConfiguration)xmlPart).setReloadingStrategy(reloadingStrategy);
			ConfigurationListener listener = new ConfigurationListener() {
				public void configurationChanged(ConfigurationEvent event) {
					try {
						if (event.getType() == AbstractFileConfiguration.EVENT_RELOAD && !event.isBeforeUpdate()) {
							EventLog.log("Configuration reloaded");
						}
					} catch (Exception e) {
						log.error("Failed to reload configuration", e);
					}
				}
			};
			xmlPart.addConfigurationListener(listener);

			loadLanguageConfig(new File(new java.net.URI(xmlPart.getURL().toString())).getParentFile(), tmpConfig);

			mainConfig = tmpConfig;
		} catch (ConfigurationException e) {
			Util.log("Invalid configuration", e);
			throw new FatalException("Invalid configuration", e);
		} catch (Exception e) {
			String message = "Failed to load configuration";
			EventLog.log(message);
			throw new FatalException(message, e);
		}
	}

	/**
	 * Add the language configuration to a combined configuration
	 * @param langFolder the folder where language configuration files can be found
	 * @param allConfigs the combined configuration that will be filled with language files
	 * @return the language codes loaded
	 * @throws URISyntaxException
	 * @throws ConfigurationException
	 */
	public static Set<String> loadLanguageConfig(File langFolder, CombinedConfiguration allConfigs)
			throws URISyntaxException, ConfigurationException {
		String s;
		Set<String> languageCodes = new HashSet<String>();
		// Language files
		// Cerco il file principale ... e carico tutti i
		// lang-*.properties della stessa folder
		FilenameFilter filter = new FilenameFilter() {
		    public boolean accept(File dir, String name) {
		        if (name.startsWith("lang-") && name.endsWith(".properties")) {
		        	return true;
		        }
		        return false;
		    }
		};

		String[] languages = langFolder.list(filter);
		for (String language : languages) {
			String langName = language.substring(language.indexOf("-") + 1, language.indexOf(".properties"));
			languageCodes.add(langName);
			s = "Adding '" + langName + "' language pack";
			log.info(s);
			// EventLog.log(s);
			// Aggiungo la configurazione per ogni lingua
			// Each language configuration is a node named langName, so the property will be accessed as /<langName>/<propertyName>
			String encodingString = Util.getEncodingString();
			PropertiesConfiguration pconf = new PropertiesConfiguration();
			pconf.setEncoding(encodingString);
			pconf.setFileName(language);
			pconf.load();
			allConfigs.addConfiguration(pconf, langName, langName);
		}
		return languageCodes;
	}
	
	/**
	 * Load a single language configuration file into the main configuration, if not there already
	 * @param languageCode the 2-letter language code
	 * @param languageFolder the folder where the configuration file is kept
	 * @throws ConfigurationException if the configuration can not be loaded
	 */
	public static void loadLanguageConfig(String languageCode, File languageFolder) throws ConfigurationException {
		if (hasLanguage(languageCode)) {
			log.debug(MessageFormatter.format("Configuration for language \"{}\" already loaded; skipping...", languageCode));
			return;
		}
		String languageFile = languageFolder.getAbsolutePath() + File.separatorChar + "lang-" + languageCode + ".properties";
		String encodingString = Util.getEncodingString();
		PropertiesConfiguration pconf = new PropertiesConfiguration();
		pconf.setEncoding(encodingString);
		pconf.setFileName(languageFile);
		pconf.load();
		((CombinedConfiguration)mainConfig).addConfiguration(pconf, languageCode, languageCode);
		
	}
	
	public static boolean hasLanguage(String languageCode) {
		return !mainConfig.subset(languageCode).isEmpty();
	}
	
	public static void reloadIfChanged() {
		// Will reload only if file is changed
		// Will check file timestamp only if RELOAD_TIMEOUT_SECONDS have passed since last check
		String previousNdc = NDC.pop();
		xmlPart.reload();
		NDC.push(previousNdc);
	}

	/**
	 * @param key
	 * @return
	 * @see org.apache.commons.configuration.HierarchicalConfiguration#configurationsAt(java.lang.String)
	 */
	public static List configurationsAt(String key) {
		return mainConfig.configurationsAt(key);
	}
	
	/**
	 * @param key
	 * @return
	 * @see org.apache.commons.configuration.AbstractConfiguration#getInt(java.lang.String)
	 */
	public static int getInt(String key) {
		return mainConfig.getInt(key);
	}

	public static int getInt(String key, int defaultValue) {
		return mainConfig.getInt(key, defaultValue);
	}

	/**
	 * @param key
	 * @return
	 * @see org.apache.commons.configuration.AbstractConfiguration#getString(java.lang.String)
	 */
	public static String getString(String key) {
		return mainConfig.getString(key);
	}

	/**
	 * @param key
	 * @return
	 * @see org.apache.commons.configuration.AbstractConfiguration#getString(java.lang.String)
	 */
	public static String getString(String key, String defaultValue) {
		return mainConfig.getString(key, defaultValue);
	}

	/**
	 * @param key
	 * @return
	 * @see org.apache.commons.configuration.AbstractConfiguration#getBoolean(java.lang.String)
	 */
	public static boolean getBoolean(String key) {
		return mainConfig.getBoolean(key);
	}

	/**
	 * @param key
	 * @param defaultValue
	 * @return
	 * @see org.apache.commons.configuration.AbstractConfiguration#getBoolean(java.lang.String, boolean)
	 */
	public static boolean getBoolean(String key, boolean defaultValue) {
		return mainConfig.getBoolean(key, defaultValue);
	}
	
	public static void addListener(ConfigurationListener listener) {
		xmlPart.addConfigurationListener(listener);
	}
	
	/**
	 * Returns the keys (without the "key." prefix) for the given language code, i.e. crop, wood, ...
	 * @param languageCode
	 * @return
	 */
	public static Iterator<String> getLanguageKeys(String languageCode) {
		return getLanguageKeys(languageCode, null);
	}

	/**
	 * Returns the keys (without the "key.<prefix>" prefix) for the given language code, i.e. troop1, troop2, ...
	 * @param languageCode
	 * @param prefix the element of the keyword following "key."
	 * @return
	 */
	public static Iterator<String> getLanguageKeys(String languageCode, String prefix) {
		return mainConfig.subset(languageCode + "/key" + (prefix!=null?"/"+prefix:"")).getKeys();
	}

	protected static XMLConfiguration getXmlConfiguration() {
		return xmlPart;
	}

	/**
	 * Returns all values defined for a given key
	 * @param key
	 * @return
	 */
	public static List<String> getStringList(String key) {
		return mainConfig.getList(key);
	}
}
